
require 'remotevirtualnode'
require 'localvirtualnode'
require 'key'
require 'thread'

# Classe principal.
# Representa o Chord com todos os seus Vnodes.  
class Chord
  # Construtor.
  # * +num_vnodes+ - Quantidade de Virtual Nodes dessa instancia do Chord.
  # * +host+ - Host que esta rodando.
  # * +start_port+ - Porta inicial.
  # * +num_successors+ - Número de sucessores.
  def initialize(num_vnodes, host, start_port, num_successors)
    @num_vnodes = num_vnodes
    @start_port = start_port
    @num_successors = num_successors
    @host = host
    @virtual_nodes = []
    @all_successors = {}
    @succs_added_callback = nil
    @succs_leaved_callback = nil
    @new_predecessor_callback = nil
    @successors_lock = Mutex.new
    @pred_lock = Mutex.new
  end

  # Inicia as operacoes do Chord.
  # Esse metodo e' chamado para criar um novo Chord ring. Caso contrario o
  # metodo Chord.join deve ser usado para ingressar em um Chord ja existente.
  # Cria um Vnode que depois serve de base para o ingresso dos outros Vnodes
  # restantes.
  def create
    @virtual_nodes << LocalVirtualNode.new(Key.new("#{@host}:#{@start_port}"),
                                                @host, @start_port, @num_successors,
                                                method(:succ_updated), method(:predecessor_update))
    @start_port += 1

    @virtual_nodes.first.create

    sleep 1

    (@num_vnodes - 1).times do
      @virtual_nodes << LocalVirtualNode.new(Key.new("#{@host}:#{@start_port}"),
                                                  @host, @start_port, @num_successors,
                                                  method(:succ_updated), method(:predecessor_update))
      @start_port += 1
      @virtual_nodes.last.join(@virtual_nodes.first)
    end

    @all_successors = {}
    @virtual_nodes.each do |node|
      @all_successors[node.node_id.key.to_s] = [] #node.get_succ_list #[]
    end
  end

  # Ingressa em um Chord ja existente atraves do host e porta passados.
  def join(host, port)
    @virtual_nodes << LocalVirtualNode.new(Key.new("#{@host}:#{@start_port}"),
                                                @host, @start_port, @num_successors,
                                                method(:succ_updated), method(:predecessor_update))
    @start_port += 1

    begin
      remote_node = RemoteVirtualNode.new('druby', host, port)
    rescue Exception => e
      raise(Exception, "Join: Remote Node Dont Exist")
    end
    @virtual_nodes.first.join(remote_node)

    (@num_vnodes - 1).times do
      @virtual_nodes << LocalVirtualNode.new(Key.new("#{@host}:#{@start_port}"),
                                                  @host, @start_port, @num_successors,
                                                  method(:succ_updated), method(:predecessor_update))
      @start_port += 1
      @virtual_nodes.last.join(@virtual_nodes.first)
    end

    @all_successors = {}
    @virtual_nodes.each do |node|
      @all_successors[node.node_id.key.to_s] = [] #node.get_succ_list #[]
    end
  end

  # Retorna o responsavel pela chave passada.
  def lookup(key)
    #TODO Otimizar para usar como ponto de partida o virtual node mais proximo da chave procurada
    hash_key = Key.new(key)
    @virtual_nodes.first.find_successor(hash_key)
  end

  # Finaliza as atividades dando um leave para cada virtual node.
  def finish
    @virtual_nodes.each do |node|
      node.leave
    end
  end

  # Imprime informacoes sobre cada virtual node dessse Chord.
  def print_nodes
    i = 0
    @virtual_nodes.each do |node|
      puts "----------------------------------------------------------------"
      puts "Node: " + i.to_s
      puts "Node ID: " + node.node_id.to_s
      begin
        puts "Node Pred:" + node.get_pred_list.node_id.to_s if node.get_pred_list != nil
      rescue Exception => e
        puts "Node Pred: NIL"
      end
      begin
        node.get_succ_list.each{|succ| puts "Node Succ:" + succ.node_id.to_s if succ != nil}
      rescue Exception => e
        puts "Node Succ: Inconsistente"
      end
      i += 1
    end
    puts '--------------------------------------------------------'
    puts ""
  end

  # Define o bloco que sera usado como callback quando novos no's forem adicionadas.
  def succs_added(&block)
    @succs_added_callback = block
  end

  # Define o bloco que sera usado como callback quando no's sairem do Chord.
  def succs_leaved(&block)
    @succs_leaved_callback = block
  end
  
  def new_predecessor(&block)
    @new_predecessor_callback = block
  end

  def each_vnode
    @virtual_nodes.each { |vnode| yield(vnode)  }
  end

private

  def predecessor_update(id, pred_id, pred_host)
    if @new_predecessor_callback != nil
      begin
        @pred_lock.lock
        @new_predecessor_callback.call(id, pred_id, pred_host)
      ensure
        @pred_lock.unlock
      end
    end
  end


  # Esse metodo e passado como callback para os Vnodes de tal forma que ao ocorrem
  # alteracoes na sua lista de sucessores ele e' chamado.
  def succ_updated(id, successors)
    
    begin
      
      @successors_lock.lock
 
      old_succs = @all_successors[id]
      new_succs = []
      
      successors.each do |succ|
        found = false
        old_succs.each do |old_succ|
          if succ[0].eql?(old_succ)
            found = true
          end
        end
        if !found
          new_succs << succ
        end
      end
    
    rescue Exception => e
      #IGNORAR
    ensure
      
      begin
      
        if new_succs
          @all_successors[id] = successors
          if @succs_added_callback != nil
            new_succs.each{|s| @succs_added_callback.call(id, s, s[0].eql?(successors.first[0]) ? true : false)}
          end
        end
      
        leaved_succs = []
  
        old_succs.each do |old_succ|
          not_found = true
          successors.each do |succ|
            if old_succ[0].eql?(succ[0])
              not_found = false
            end
          end
          if not_found
            leaved_succs << old_succ
          end
        end    

      
       if leaved_succs
         @all_successors[id] = successors
      
         if @succs_leaved_callback != nil
           leaved_succs.each { |s| @succs_leaved_callback.call(id, s, s[0].eql?(old_succs.first[0]) ? true : false) }
         end
       end
       
       rescue Exception => e
        #IGNORA
       end
      
      @successors_lock.unlock
    end

  end
end
